package com.adeng.springbootdemo.aspect;

import com.adeng.springbootdemo.utils.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;

/**
 * 日志拦截
 *
 * @author hzxuanjunqi
 */
@Component
@Aspect
@Slf4j
@Order(3)
public class ApiLogAspect {

    // @Around("@annotation(com.netease.loan.kylin.platform.quhua.utils.log.MvcControllerMethodLog)")
    // public Object monitorClass(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    // return doLog(proceedingJoinPoint);
    // }

    @Around("within(com.adeng..controller..*) " +
            " && (@annotation(com.adeng.springbootdemo.annotation.MvcControllerMethodLog))")
    public Object allControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getSignature().getDeclaringType().getSimpleName();
        String methodName = pjp.getSignature().getName();
        Object[] objs = pjp.getArgs();

        return doLog(pjp);
    }

    /**
     * 对接口调用进行日志记录
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    protected Object doLog(ProceedingJoinPoint pjp) throws Throwable {
        String argstr = null;
        Timestamp createTime = DateUtil.getCurrentTimestamp();
        String interfaceNameStr = getInterfaceName(pjp);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if ("GET".equals(request.getMethod())) {
            StringBuilder stringBuilder = getArgs(pjp.getArgs());
            stringBuilder.append(request.getParameter("requestId")).append(";");
            argstr = stringBuilder.toString();
        } else {
            argstr = getArgs(pjp.getArgs()).toString();
        }

        String os = "";
        String appVersion = "";
        try {
            JSONObject jsonObject1 = JSONObject.parseObject(new String(Base64.decodeBase64(request.getHeader("appMeta"))));
            os = jsonObject1.getString("OS");
            appVersion = jsonObject1.getString("appVersion");
        } catch (Exception e) {
        }

        try {
            writeLog(pjp);
            // 执行方法调用
            Object result = pjp.proceed(pjp.getArgs());
            int elapsedTime = (int) ((DateUtil.getCurrentTimestamp().getTime() - createTime.getTime()));
            writeLog(interfaceNameStr.toString(), argstr.toString(), JSON.toJSONString(result), createTime, elapsedTime, os, appVersion);
            return result;
        } catch (Throwable e) {
            int elapsedTime = (int) ((DateUtil.getCurrentTimestamp().getTime() - createTime.getTime()));
            log.error("\n\r==========error in executing 【" + interfaceNameStr + "】(start:" + createTime
                    + ", elapsedTime:" + elapsedTime + "ms)" + "INPUT PARAMS: 【" + argstr + "】" + "ERROR MESSAGE: 【"
                    + e.toString() + "】.==============", e);
            throw e;
        }
    }


    /**
     * 获取接口名称
     *
     * @param pjp
     * @return
     */
    private String getInterfaceName(ProceedingJoinPoint pjp) {
        StringBuilder interfaceNameSb = new StringBuilder().append(pjp.getTarget().getClass().getName()).append(".").append(pjp.getSignature().getName()).append("(").append(getArgNames(pjp.getArgs())).append(")");
        return interfaceNameSb.toString();
    }

    /**
     * 获取参数类型名称
     *
     * @param args
     * @return 参数名, 参数名, 参数名
     */
    private String getArgNames(Object[] args) {
        try {
            if (ArrayUtils.isNotEmpty(args)) {
                StringBuilder sb = new StringBuilder();
                for (Object arg : args) {
                    if (null == arg) {
                        sb.append("null,");
                    } else {
                        sb.append(arg.getClass().getSimpleName()).append(",");
                    }
                }
                if (sb.length() > 0) {
                    sb.deleteCharAt(sb.length() - 1);
                }
                return sb.toString();
            }
        } catch (Throwable e) {
            log.error("ApiLogAspect.getArgNames ex=" + e.getMessage(), e);
        }
        return "";
    }

    /**
     * 获取参数字符串
     *
     * @param args
     * @return
     */
    private StringBuilder getArgs(Object[] args) {
        StringBuilder argSb = new StringBuilder();
        if (null != args && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof ServletResponse || args[i] instanceof ServletRequest) {
                    continue;
                }
                // 获取入参JSON字符串
                argSb.append(JSON.toJSONString(args[i])).append(";");
            }
        }
        return argSb;
    }

    /**
     * 将api调用日志写入
     *
     * @param api
     * @param param
     * @param result
     * @param createTime
     * @param elapsedTime
     */
    private void writeLog(String api, String param, String result, Timestamp createTime, int elapsedTime, String os, String appVersion) {
        /**
         * 未来可以扩展到表
         */
        log.info("**********************api={},param={},result={},createTime={},elapsedTime={}, os={}, appVersion={}", api, param, result,
                createTime, elapsedTime, os, appVersion);

    }

    private void writeLog(ProceedingJoinPoint pjp) {
        String methodName = pjp.getSignature().getName();
        String className = pjp.getSignature().getDeclaringType().getSimpleName();

        String logStr = String.format("[%s][%s][%s]:[%s] ",
                className, methodName, getArgNames(pjp.getArgs()), getArgs(pjp.getArgs()));

        log.info(logStr);
    }
}
